import java.io.*;
import java.net.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.Map.Entry;

public class Client1 extends Thread {
	private Socket _socket;
	private BufferedReader _input;
	private PrintWriter _output;
	private final int _bufferSize = 1024;
	public static PeerSet peer_set = new PeerSet();
	public static FileSet file_set = new FileSet();

	
	private boolean deconnexion(Peer p) {

		char[] buffer = new char[_bufferSize];
		final int read_length = 1;
		int offset = 0;
		int readc;

		try {
			if ((readc = _input.read(buffer, offset, read_length)) < 1)
				offset += readc;
		} catch (Exception e) {
			p = peer_set.PeerMap.get(p.getKey());
			for (String file_key : p.leeches) {
				if (file_set.FileMap.containsKey(file_key)) {
					File f = file_set.FileMap.get(file_key);
					if (f.peers.contains(p.getKey()) && f.peers.size() == 1) {
						file_set.FileDel(file_key);
					} else if (f.peers.contains(p.getKey())) {
						f.peers.remove(p.getKey());
						file_set.FileMap.put(file_key, f);
					}
				}
			}
			
			
			for (String file_key : p.seeds) {
				
				if (file_set.FileMap.containsKey(file_key)) {
					
					File f = file_set.FileMap.get(file_key);
					if (f.peers.contains(p.getKey()) && f.peers.size() == 1) {
						
						file_set.FileDel(file_key);
					} else if (f.peers.contains(p.getKey())) {
						f.peers.remove(p.getKey());
						file_set.FileMap.put(file_key, f);
					}
				}
			}
			
			// Suppression du peer de la hashmap des peers (PeerSet)
			peer_set.PeerDel(p.getKey());
		}

	
		return true;
	}



	// announce renvoie vrai si "command" est une annonce correctement
	// constituée et si oui elle remplit les structures de données _peers et
	// _files au fur et à mesure
	private boolean announce(String[] command) {
		int i = 0; // Indice pour la lecture des paramètres
		if (!command[i++].equals("announce")) {
			return false;
		}

		if (!command[i++].equals("listen")) {
			return false;
		}
		int port;
		try {
			port = Integer.parseInt(command[i++]);
		} catch (Exception e) {
			return false;
			/*
			 * Si une exception est levée cela veut dire que la conversion de
			 * command[2] en int n'a pas fonctionné et la commande est donc
			 * incorrecte
			 */
		}

		Peer new_peer = new Peer(_socket.getInetAddress().getHostAddress(),
				_socket.getPort(), port);

		if (!command[i++].equals("seed")) {
			return false;
		}
		if (!command[i++].equals("[")) {
			return false;
		}

		while (!command[i].equals("]")) { // On fait une boucle sur tous les
			// fichiers seedés

			/** FileName **/
			String nomSeed = command[i];
			i++;

			/** FileSize **/
			int FileLength;
			try {
				FileLength = Integer.parseInt(command[i++]);
			} catch (Exception e) {
				/* Si la longueur n'est pas un entier => commande incorrecte */
				return false;
			}

			// Taille des pièces et key à lire pour terminer la lecture
			// d'un fichier seedé

			/** PieceSize **/
			int PieceSize;
			try {
				PieceSize = Integer.parseInt(command[i++]);

			} catch (Exception e) {
				return false;
			}

			/** Key **/
			String Key;
			try {
				Key = command[i++];
				if (Key.length() != 32)
					throw new Exception();
			} catch (Exception e) {
				return false;
			}

			/** Création d'une instance de fichier **/
			File new_file = new File(nomSeed, FileLength, PieceSize);
			if (file_set.FileIsIn(Key)) {
				new_file = file_set.FileMap.get(Key);

			}
			new_file.addPeer(new_peer);
			/** Ajout du fichier ds la hashmap de FileSet **/
			file_set.FileMap.put(Key, new_file);

			/** Ajout du fichier ds la liste des fichiers du peer **/
			new_peer.addSeed(Key);
		}

		/**** ON PASSE AUX LEECH ****/
		i++;

		if (!command[i++].equals("leech")) {
			return false;
		}

		if (!command[i++].equals("[")) {
			return false;
		}

		while (!(command[i].substring(0, 1).equals("]"))) { // On fait une
			// boucle sur tous
			// les fichiers
			// leechés

			/** FileName **/
			String nomLeech = command[i++];

			/** FileSize **/
			int FileLength;
			try {
				FileLength = Integer.parseInt(command[i++]);
			} catch (Exception e) {
				/* Si la longueur n'est pas un entier => commande incorrecte */
				return false;
			}

			/** PieceSize **/
			int PieceSize;
			try {
				PieceSize = Integer.parseInt(command[i++]);

			} catch (Exception e) {
				return false;
			}

			/** Key **/
			String Key;
			try {
				Key = command[i++];
				if (Key.length() != 32)
					throw new Exception();
			} catch (Exception e) {
				return false;
			}

			/** Création d'une instance de fichier **/
			File new_file = new File(nomLeech, FileLength, PieceSize);
			if (file_set.FileIsIn(Key)) {
				new_file = file_set.FileMap.get(Key);

			}
			new_file.addPeer(new_peer);
			/** Ajout du fichier ds la hashmap de FileSet **/
			file_set.FileMap.put(Key, new_file);

			/** Ajout du fichier ds la liste des fichiers du peer **/
			new_peer.addLeech(Key);
		}

		peer_set.PeerAdd(new_peer.getKey(), new_peer);

		return true;
	}

	// TODO faire d'autres fonctions comme isAnnounce pour compélter la
	// structure de données

	private boolean getfile(String[] command) {
		int i = 0; // Indice pour la lecture des paramètres
		if (!command[i++].equals("getfile")) {
			return false;
		}
		String Key = new String();
		String response = new String();
		try {
			Key = command[i++];
			if (Key.length() != 32)
				throw new Exception();
		} catch (Exception e) {
			return false;
		}
		response = "peers " + Key + " [ ";
		if (file_set.FileIsIn(Key)) {
			File get_file = file_set.FileMap.get(Key);
			Peer new_peer;
			for (String key_peer : get_file.peers) {
				new_peer = peer_set.PeerMap.get(key_peer);
				response += new_peer.getIp() + " : " + new_peer.getListenPort()
						+ " ";
			}
		}
		response += "] ";
		_output.write(response, 0, response.length());
		_output.flush();
		return true;
	}

	private boolean look(String[] command) {
		int i = 0;
		String response = new String();
		if (!command[i++].equals("look")) {
			return false;
		}
		if (!command[i++].equals("[")) {
			return false;
		}

		if (!command[i++].equals("filename")) {
			return false;
		}
		if (!command[i++].equals("=")) {
			return false;
		}
		if (!command[i++].equals("\"")) 			
			return false;
		
		String filename = new String();
		filename = command[i];

		if (!command[i++].equals("\"")) {
			return false;
		}
		
		response += "list [ ";
		for (String key: file_set.FileMap.keySet()){
			if (file_set.FileMap.get(key).name.equals(filename)){
				File f = file_set.FileMap.get(key);
				response += f.name + " "+ f.fileSize + " "+ f.pieceSize + " " + key +" ";
			}
		}
		response += "] ";
		_output.write(response, 0, response.length());
		_output.flush();

		return true;
	}

	private boolean update(String[] command) {
		int i = 0; // Indice pour la lecture des paramètres
		if (!command[i++].equals("update")) {
			return false;
		}
		Peer new_peer = new Peer(_socket.getInetAddress().getHostAddress(),
				_socket.getPort());
		new_peer = peer_set.PeerMap.get(new_peer.getKey());
		List<String> new_seed = new ArrayList<String>();
		List<String> new_leech = new ArrayList<String>();
		if (!command[i++].equals("seed")) {
			return false;
		}
		if (!command[i++].equals("[")) {
			return false;
		}
		while (!command[i].equals("]")) {
			String Key = new String();
			try {
				Key = command[i++];
				if (Key.length() != 32)
					throw new Exception();
			} catch (Exception e) {
				return false;
			}
			File new_file = file_set.FileMap.get(Key);
			new_file.addPeer(new_peer);
			file_set.FileMap.put(Key, new_file);
			new_seed.add(Key);
		}

		i++;

		if (!command[i++].equals("leech")) {
			return false;
		}

		if (!command[i++].equals("[")) {
			return false;
		}

		while (!(command[i].substring(0, 1).equals("]"))) {
			String Key = new String();
			try {
				Key = command[i++];
				if (Key.length() != 32)
					throw new Exception();
			} catch (Exception e) {
				return false;
			}
			File new_file = file_set.FileMap.get(Key);
			new_file.addPeer(new_peer);
			file_set.FileMap.put(Key, new_file);
			new_leech.add(Key);
		}
		new_peer.leeches = new_leech;
		new_peer.seeds = new_seed;
		peer_set.PeerMap.put(new_peer.getKey(), new_peer);
		return true;
	}

	/**
	 * Constructeur
	 * 
	 * @param s
	 * @param peers
	 * @param files
	 */
	Client1(Socket s, PeerSet p, FileSet f) { // TODO types à modifier, ce sont
		// les hashmap qu'il faut passer
		_socket = s;
		// _files = files;
		// _peers = peers;
		peer_set = p;
		file_set = f;
	}

	/**
	 * Lance le serveur.
	 */
	public void run() {
		try {
			_input = new BufferedReader(new InputStreamReader(
					_socket.getInputStream()));
			_output = new PrintWriter(new BufferedWriter(
					new OutputStreamWriter(_socket.getOutputStream())));
		} catch (Exception e) {
			System.out.println("Connexion Impossible");
			return;
		}
		Peer new_peer = new Peer(_socket.getInetAddress().getHostAddress(),
				_socket.getPort());
		char[] buffer = new char[_bufferSize];
		String sbuffer = new String(); // Le même buffer que ci-dessus mais en
		// string
		String[] splitted = new String[_bufferSize]; // Le buffer splitté sur
		// les espaces
		String response = ""; // La commande de réponse
		final int read_length = 1; // Nombre d'octets à lire
		int offset = 0; // Le décalage dans le buffer (forcément multiple de
		// read_length)
		int readc; // Le nombre d'octets lus à chaque tour (compris entre -1 et
		// read_length)

		/*** Readings ***/
		while (true) {
			try {
				if ((readc = _input.read(buffer, offset, read_length)) < 1)
					break; // Connexion fermée ou échouée, on sort

				System.out.println(buffer);
				offset += readc;

				sbuffer = new String(buffer);
				if (sbuffer.startsWith(" "))
					sbuffer = sbuffer.substring(1);
				splitted = sbuffer.split(" ");

				if (splitted[0].equals("announce")) {

					if (announce(splitted)) {
						System.out.println("############ ANNOUNCE ACKNOWLEDGED");
						response += "ok ";

						_output.write(response, 0, response.length());
						_output.flush();
						response = "";
						buffer = new char[_bufferSize];
						offset = 0; /*
						 * Dès qu'on a reconnu une commande entière
						 * on efface le buffer en mettant l'offset à
						 * zéro : TRES IMPORTANT
						 */

					} 

				} else if (splitted[0].equals("getfile") && splitted.length > 1
						&& splitted[1].length() == 32) { 
					if (getfile(splitted)) {
						buffer = new char[_bufferSize];
						offset = 0;
						response = "";
					}
				}

				else if (splitted[0].equals("update")) { // Réponse toute faite
					// pour tester peer
					int occ = 0;
					for (String s : splitted)
						if (s.equals("]"))
							occ++;
					if (occ == 2) {
						if (update(splitted)) {
							response += "ok ";

						
							_output.write(response, 0, response.length());
							_output.flush();
							buffer = new char[_bufferSize];
							offset = 0;
							response = "";
						}
					}
				} else if (splitted[0].equals("look")) {
					if (look(splitted)) {
						
						System.out.println("Lol");
			
						buffer = new char[_bufferSize];
						offset = 0;
						response = "";
					}
				}

				else {
					if (offset >= _bufferSize) {
						System.out
						.println("Command unrecognized, flushing buffer");
						buffer = new char[_bufferSize];
						offset = 0;
						response = "";
					}
				}
			}

			catch (Exception e) {
				System.out.println("Error while reading: " + e.toString());
				break;
			}

		}

		/*** Close connection ***/
		try {
			_input.close();
			_socket.close();
		} catch (Exception e) {
			System.out.println("Error while closing connection: "
					+ e.toString());
			return;
		}
		deconnexion(new_peer);
		System.out.println("Client exiting");
	}
}
